/*
 * Copyright 2020-2022 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package sample.config;

import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.server.authorization.*;
import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
import org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OAuth2AuthorizationServerConfigurer;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.settings.ClientSettings;
import org.springframework.security.oauth2.server.authorization.settings.OAuth2TokenFormat;
import org.springframework.security.oauth2.server.authorization.settings.TokenSettings;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.web.authentication.*;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;
import org.springframework.security.web.util.matcher.RequestMatcher;
import sample.captcha.OpenAuthCaptchaAuthenticationConverter;
import sample.captcha.OpenAuthCaptchaAuthenticationProvider;
import sample.jose.Jwks;
import sample.password.OpenAuthPasswordAuthenticationConverter;
import sample.password.OpenAuthPasswordAuthenticationProvider;

import java.time.Duration;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;

/**
 * @author Joe Grandja
 * @author Daniel Garnier-Moiroux
 */
@Configuration(proxyBeanMethods = false)
public class AuthorizationServerConfig {
	private static final String CUSTOM_CONSENT_PAGE_URI = "/oauth2/consent";
	/**
	 * 刷新令牌有效期默认 30 天
	 */
	private final static int refreshTokenValiditySeconds = 60 * 60 * 24 * 30;

	/**
	 * 请求令牌有效期默认 12 小时
	 */
	private final static int accessTokenValiditySeconds = 60 * 60 * 12;

	@Bean
	@Order(Ordered.HIGHEST_PRECEDENCE)
	public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
		OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
		authorizationServerConfigurer
				.tokenEndpoint((tokenEndpoint) -> {// 个性化认证授权端点
							tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter()); // 注入自定义的授权认证Converter
//									.accessTokenResponseHandler(successEventHandler) // 登录成功处理器
//									.errorResponseHandler(failureEventHandler);
				})
				.authorizationEndpoint(authorizationEndpoint ->
						authorizationEndpoint.consentPage(CUSTOM_CONSENT_PAGE_URI))
				.oidc(Customizer.withDefaults());	// Enable OpenID Connect 1.0

		RequestMatcher endpointsMatcher = authorizationServerConfigurer
				.getEndpointsMatcher();

		DefaultSecurityFilterChain securityFilterChain = http
			.requestMatcher(endpointsMatcher)
			.authorizeRequests(authorizeRequests ->
				authorizeRequests.anyRequest().authenticated()
			)
			.csrf(csrf -> csrf.ignoringRequestMatchers(endpointsMatcher))
			.exceptionHandling(exceptions ->
				exceptions.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
			)
			.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
			.apply(authorizationServerConfigurer).and().build();

		// 注入自定义授权模式实现
		addCustomOAuth2GrantAuthenticationProvider(http);
		return securityFilterChain;
	}

	// @formatter:off
	/**
	 * 注册客户端
	 *
	 * @param jdbcTemplate 操作数据库
	 * @return 客户端仓库
	 */
	@Bean
	public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) {

		// 操作数据库对象
		JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate);

		//创建客户端。
		String clientId_ac = "authorization-code";
		String clientId_pass = "password";
		String clientId_cc = "client_credentials";

		//将RegisteredClient保存到数据库中
		this.registClient(registeredClientRepository, "authorization-code", 1);
		this.registClient(registeredClientRepository, "client_credentials", 2);
		this.registClient(registeredClientRepository, "password", 3);
		this.registClient(registeredClientRepository, "captcha", 4);
		return registeredClientRepository;

//		return new InMemoryRegisteredClientRepository(registeredClient);
	}
	// @formatter:on

	/**
	 * 加载jwk资源
	 * 用于生成令牌
	 * @return
	 */
	@Bean
	public JWKSource<SecurityContext> jwkSource() {
		RSAKey rsaKey = Jwks.generateRsa();
		JWKSet jwkSet = new JWKSet(rsaKey);
		return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
	}

	@Bean
	public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) {
		return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource);
	}

	/**
	 * <p>授权服务器元信息配置</p>
	 * <p>
	 * 授权服务器本身也提供了一个配置工具来配置其元信息，大多数都使用默认配置即可，唯一需要配置的其实只有授权服务器的地址issuer
	 * 在生产中这个地方应该配置为域名
	 *
	 * @return
	 */
	@Bean
	public AuthorizationServerSettings authorizationServerSettings() {
		return AuthorizationServerSettings.builder().build();
	}

	/**
	 * 令牌的发放记录
	 *
	 * @param jdbcTemplate               操作数据库
	 * @param registeredClientRepository 客户端仓库
	 * @return 授权服务
	 */
	@Bean
	public OAuth2AuthorizationService auth2AuthorizationService(
			JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationService(jdbcTemplate, registeredClientRepository);
	}

	/**
	 * 把资源拥有者授权确认操作保存到数据库
	 * 资源拥有者（Resource Owner）对客户端的授权记录
	 *
	 * @param jdbcTemplate               操作数据库
	 * @param registeredClientRepository 客户端仓库
	 * @return
	 */
	@Bean
	public OAuth2AuthorizationConsentService auth2AuthorizationConsentService(
			JdbcTemplate jdbcTemplate,
			RegisteredClientRepository registeredClientRepository) {
		return new JdbcOAuth2AuthorizationConsentService(jdbcTemplate, registeredClientRepository);
	}


	private void registClient(JdbcRegisteredClientRepository registeredClientRepository, String clientId, Integer type) {
		// 查询客户端是否存在
		RegisteredClient registeredClient = registeredClientRepository.findByClientId(clientId);
		if (registeredClient == null) {
			if (type == 1) {
				registeredClient = this.createRegisteredClientAuthorizationCode(clientId);
			} else if (type == 2) {
				registeredClient = this.createRegisteredClientClientCredentials(clientId);
			} else if (type == 3) {
				registeredClient = this.createRegisteredClientPassword(clientId);
			} else if (type == 4) {
				registeredClient = this.createRegisteredClientCaptcha(clientId);
			}
			registeredClientRepository.save(registeredClient);
		}
	}

	/**
	 * 定义客户端（令牌申请方式：授权码模式）
	 *
	 * @param clientId 客户端ID
	 * @return
	 */
	private RegisteredClient createRegisteredClientAuthorizationCode(final String clientId) {
		// JWT（Json Web Token）的配置项：TTL、是否复用refrechToken等等
		TokenSettings tokenSettings = TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
				.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValiditySeconds))
				.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValiditySeconds))
				.build();
		// 客户端相关配置
		ClientSettings clientSettings = ClientSettings.builder()
				// 是否需要用户授权确认
				.requireAuthorizationConsent(false)
				.build();

		RegisteredClient.Builder builder = RegisteredClient.withId(clientId)
				.clientId(clientId)
				.clientSecret(SecurityConstants.NOOP + clientId)
				.clientAuthenticationMethods(clientAuthenticationMethods -> {
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
				});

		// 授权模式
		builder.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE);
		builder.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN);
		// 回调地址
		builder.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc");
		builder.redirectUri("http://127.0.0.1:8080/authorized");
		// scope
		builder.scope(OidcScopes.OPENID);
		builder.scope(OidcScopes.PROFILE);
		// 注入扩展配置
		builder.clientSettings(clientSettings);
		// token配置
		builder.tokenSettings(tokenSettings);
		return builder.build();
	}

	/**
	 * 定义客户端（令牌申请方式：客户端模式）
	 *
	 * @param clientId 客户端ID
	 * @return
	 */
	private RegisteredClient createRegisteredClientClientCredentials(final String clientId) {
		// JWT（Json Web Token）的配置项：TTL、是否复用refrechToken等等
		TokenSettings tokenSettings = TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
				.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValiditySeconds))
				.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValiditySeconds))
				.build();
		// 客户端相关配置
		ClientSettings clientSettings = ClientSettings.builder()
				// 是否需要用户授权确认
				.requireAuthorizationConsent(false)
				.build();

		RegisteredClient.Builder builder = RegisteredClient.withId(clientId)
			.clientId(clientId)
			.clientSecret(SecurityConstants.NOOP + clientId)
			.clientAuthenticationMethods(clientAuthenticationMethods -> {
				clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
				clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
			});

		// 授权模式
		builder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
		builder.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN);
		// 回调地址
		builder.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc");
		builder.redirectUri("http://127.0.0.1:8080/authorized");
		// scope
		builder.scope(OidcScopes.OPENID);
		builder.scope(OidcScopes.PROFILE);
		// 注入扩展配置
		builder.clientSettings(clientSettings);
		// token配置
		builder.tokenSettings(tokenSettings);
		return builder.build();
	}

	/**
	 * 定义客户端（令牌申请方式：用户名密码模式）
	 *
	 * @param clientId 客户端ID
	 * @return
	 */
	private RegisteredClient createRegisteredClientPassword(final String clientId) {
		// JWT（Json Web Token）的配置项：TTL、是否复用refrechToken等等
		TokenSettings tokenSettings = TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
				.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValiditySeconds))
				.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValiditySeconds))
				.build();
		// 客户端相关配置
		ClientSettings clientSettings = ClientSettings.builder()
				// 是否需要用户授权确认
				.requireAuthorizationConsent(false)
				.build();

		RegisteredClient.Builder builder = RegisteredClient.withId(clientId)
				.clientId(clientId)
				.clientSecret(SecurityConstants.NOOP + clientId)
				.clientAuthenticationMethods(clientAuthenticationMethods -> {
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
				});

		// 授权模式
		builder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
		builder.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN);
		builder.authorizationGrantType(AuthorizationGrantType.PASSWORD);
		// 回调地址
		builder.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc");
		builder.redirectUri("http://127.0.0.1:8080/authorized");
		// scope
		builder.scope(OidcScopes.OPENID);
		builder.scope(OidcScopes.PROFILE);
		// 注入扩展配置
		builder.clientSettings(clientSettings);
		// token配置
		builder.tokenSettings(tokenSettings);
		return builder.build();
	}

	/**
	 * 定义客户端（令牌申请方式：用户名密码模式 - 带图片验证码）
	 *
	 * @param clientId 客户端ID
	 * @return
	 */
	private RegisteredClient createRegisteredClientCaptcha(final String clientId) {
		// JWT（Json Web Token）的配置项：TTL、是否复用refrechToken等等
		TokenSettings tokenSettings = TokenSettings.builder().accessTokenFormat(OAuth2TokenFormat.REFERENCE)
				.accessTokenTimeToLive(Duration.ofSeconds(accessTokenValiditySeconds))
				.refreshTokenTimeToLive(Duration.ofSeconds(refreshTokenValiditySeconds))
				.build();
		// 客户端相关配置
		ClientSettings clientSettings = ClientSettings.builder()
				// 是否需要用户授权确认
				.requireAuthorizationConsent(false)
				.build();

		RegisteredClient.Builder builder = RegisteredClient.withId(clientId)
				.clientId(clientId)
				.clientSecret(SecurityConstants.NOOP + clientId)
				.clientAuthenticationMethods(clientAuthenticationMethods -> {
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC);
					clientAuthenticationMethods.add(ClientAuthenticationMethod.CLIENT_SECRET_POST);
				});

		// 授权模式
		builder.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS);
		builder.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN);
		builder.authorizationGrantType(new AuthorizationGrantType(SecurityConstants.CAPTCHA_GRANT_TYPE));
		// 回调地址
		builder.redirectUri("http://127.0.0.1:8080/login/oauth2/code/messaging-client-oidc");
		builder.redirectUri("http://127.0.0.1:8080/authorized");
		// scope
		builder.scope(OidcScopes.OPENID);
		builder.scope(OidcScopes.PROFILE);
		// 注入扩展配置
		builder.clientSettings(clientSettings);
		// token配置
		builder.tokenSettings(tokenSettings);
		return builder.build();
	}
	/**
	 * request -> xToken 注入请求转换器
	 * @return DelegatingAuthenticationConverter
	 */
	private AuthenticationConverter accessTokenRequestConverter() {
		return new DelegatingAuthenticationConverter(Arrays.asList(
				new OpenAuthPasswordAuthenticationConverter(),
				new OAuth2RefreshTokenAuthenticationConverter(),
				new OAuth2ClientCredentialsAuthenticationConverter(),
				new OAuth2AuthorizationCodeAuthenticationConverter(),
				new OAuth2AuthorizationCodeRequestAuthenticationConverter(),
				new OpenAuthCaptchaAuthenticationConverter()));
	}

	/**
	 * 注入授权模式实现提供方
	 *
	 * 1. 密码模式 </br>
	 * 2. 短信登录 </br>
	 *
	 */
	@SuppressWarnings("unchecked")
	private void addCustomOAuth2GrantAuthenticationProvider(HttpSecurity http) {
		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
		OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class);

		OpenAuthPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider =
				new OpenAuthPasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator);

		OpenAuthCaptchaAuthenticationProvider resourceOwnerCaptchaAuthenticationProvider =
				new OpenAuthCaptchaAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator);

		// This will add new authentication provider in the list of existing authentication providers.
		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
		http.authenticationProvider(resourceOwnerCaptchaAuthenticationProvider);

//		AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class);
//		OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class);
//
//		OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = new OAuth2ResourceOwnerPasswordAuthenticationProvider(
//				authenticationManager, authorizationService, oAuth2TokenGenerator());
//
//		OAuth2ResourceOwnerSmsAuthenticationProvider resourceOwnerSmsAuthenticationProvider = new OAuth2ResourceOwnerSmsAuthenticationProvider(
//				authenticationManager, authorizationService, oAuth2TokenGenerator());
//
//		// 处理 UsernamePasswordAuthenticationToken
//		http.authenticationProvider(new PigxDaoAuthenticationProvider());
//		// 处理 OAuth2ResourceOwnerPasswordAuthenticationToken
//		http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider);
//		// 处理 OAuth2ResourceOwnerSmsAuthenticationToken
//		http.authenticationProvider(resourceOwnerSmsAuthenticationProvider);
	}

}
